﻿Namespace Functions

    Public Interface IMetisNamespaceReference
        Inherits IMetisCommonEntity
        Property [Namespace]() As MetisNamespace

    End Interface

    ''' <summary>Represents a Namespace within Metis.</summary>
    ''' <remarks>Namespaces are used to organise Functions. Through this concept it is possible to create functions with the same name under different categories.</remarks>
    Public Class MetisNamespace
        Implements System.IComparable(Of MetisNamespace), IMetisCommonEntity, IMetisNamespaceReference, System.ComponentModel.IEditableObject


        Private _ID As Integer
        Private _Name As String
        Private _Annotation As String
        Private _Parent As MetisNamespace

        Private _BackupName As String
        Private _BackupAnnotation As String
        Private _BackupParent As MetisNamespace

        Private _IsEditMode As Boolean = False

        Private WithEvents _Children As MetisNamespaceList
        Private _MetisCore As MetisCore

        ''' <summary>Raised when a property of the Namespace has been modified.</summary>
        ''' <param name="sender">The object whose property has been changed.</param>
        ''' <param name="e">Encapsulates the name of the changed property.</param>
        Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged

        ''' <summary>Raised when a item has been changed, added or removed from the list of child Namesapces.</summary>
        ''' <param name="sender">The Namespace whose childlist has changed.</param>
        ''' <param name="e">Additional information about the kind of change.</param>
        Public Event ChildListChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ListChangedEventArgs)

        Private Sub HandleChildListChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ListChangedEventArgs) Handles _Children.ListChanged
            If e.ListChangedType = System.ComponentModel.ListChangedType.ItemAdded OrElse _
               e.ListChangedType = System.ComponentModel.ListChangedType.ItemDeleted OrElse _
               e.ListChangedType = System.ComponentModel.ListChangedType.ItemMoved OrElse _
               e.ListChangedType = System.ComponentModel.ListChangedType.ItemChanged Then
                RaiseEvent ChildListChanged(sender, e)
            End If
        End Sub

        ''' <summary>Creates a new empty Namespace instance.</summary>
        ''' <param name="MetisCore">Reference to the central management object of Metis that manages the access to the DB.</param>
        Sub New(ByVal MetisCore As Core.MetisCore)
            Me.New(MetisCore, MetisCore.GetTemporaryID, "", Nothing, "")
        End Sub

        ''' <summary>Creates a new Namespace instance.</summary>
        ''' <param name="MetisCore">Reference to the central management object of Metis that manages the access to the DB.</param>
        ''' <param name="ID">DB-ID of the Namespace</param>
        ''' <param name="Name">Name of the Namespace</param>
        Sub New(ByVal MetisCore As Core.MetisCore, ByVal ID As Integer, ByVal Name As String)
            Me.New(MetisCore, ID, Name, Nothing, "")
        End Sub

        ''' <summary>Creates a new Namespace instance.</summary>
        ''' <param name="MetisCore">Reference to the central management object of Metis that manages the access to the DB.</param>
        ''' <param name="ID">DB-ID of the Namespace</param>
        ''' <param name="Name">Name of the Namespace</param>
        ''' <param name="Parent">Reference to the parent Namespace</param>
        ''' <remarks>Specifing a parent will not add the new Namespace object to its child list!</remarks>
        Sub New(ByVal MetisCore As Core.MetisCore, ByVal ID As Integer, ByVal Name As String, ByVal Parent As MetisNamespace)
            Me.New(MetisCore, ID, Name, Parent, "")
        End Sub

        ''' <summary>Creates a new Namespace instance.</summary>
        ''' <param name="MetisCore">Reference to the central management object of Metis that manages the access to the DB.</param>
        ''' <param name="ID">DB-ID of the Namespace</param>
        ''' <param name="Name">Name of the Namespace</param>
        ''' <param name="Parent">Reference to the parent Namespace</param>
        ''' <param name="Annotation">Any comment to that Namespace.</param>
        ''' <remarks>Specifing a parent will not add the new Namespace object to its child list!</remarks>
        Sub New(ByVal MetisCore As Core.MetisCore, ByVal ID As Integer, ByVal Name As String, ByVal Parent As MetisNamespace, ByVal Annotation As String)
            _MetisCore = MetisCore
            _ID = ID
            _Name = Name
            _BackupName = _Name
            _Parent = Parent
            _BackupParent = _Parent
            _Annotation = Annotation
            _BackupAnnotation = _Annotation
            _Children = New MetisNamespaceList(MetisCore, Me)
        End Sub

        Public Function GetNextID() As Integer Implements IMetisCommonEntity.GetNextID
            _ID = _MetisCore.GetNextID
            RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("ID"))
            Return _ID
        End Function

        ''' <summary>The DB-ID of the Namespace.</summary>
        ''' <remarks>Negative if the Namespace object represents a new, not yet saved Namespace.</remarks>
        Public ReadOnly Property ID() As Integer Implements IMetisCommonEntity.ID
            Get
                Return _ID
            End Get
        End Property

        ''' <summary>Name of the Namespace</summary>
        ''' <exception cref="Exceptions.DublicateNameException">Raised when the name is already used by an item in the child list of its parent.</exception>
        ''' <exception cref="Exceptions.InvalidNameException">Raised when the name starts with a number, contains other charachters than digits, letters and '_' or exceeds a length of 64</exception>
        Public Property Name() As String Implements IMetisCommonEntity.Name
            Get
                Return _Name
            End Get
            Set(ByVal value As String)
                If MetisCore.IsValidFieldName(value, 64) Then
                    Dim ParentList As MetisNamespaceList
                    If _Parent Is Nothing Then
                        ParentList = _MetisCore.Namespaces
                    Else
                        ParentList = _Parent.Children
                    End If

                    For Each item As IMetisNamespaceReference In ParentList
                        If Not Me.Equals(item) AndAlso value.Equals(item.Name, StringComparison.OrdinalIgnoreCase) Then
                            Throw New Exceptions.DublicateNameException(value)
                        End If
                    Next
                    _Name = value
                    If Not _IsEditMode AndAlso _Name <> _BackupName Then RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Name"))
                Else
                    Throw New Exceptions.InvalidNameException(value)
                End If
            End Set
        End Property

        ''' <summary>Any comment to that Namespace.</summary>
        ''' <remarks>The annotation should not exceed 4000 characters.</remarks>
        Public Property Annotation() As String Implements IMetisCommonEntity.Annotation
            Get
                Return _Annotation
            End Get
            Set(ByVal value As String)
                _Annotation = value
                If Not _IsEditMode AndAlso _Annotation <> _BackupAnnotation Then RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Annotation"))
            End Set
        End Property

        ''' <summary>Reference to the parent Namespace</summary>
        Public Property Parent() As MetisNamespace Implements IMetisNamespaceReference.Namespace
            Get
                Return _Parent
            End Get
            Set(ByVal value As MetisNamespace)
                _Parent = value
                If Not _IsEditMode Then RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Parent"))
            End Set
        End Property

        ''' <summary>Descendants of that Namespace</summary>
        Public ReadOnly Property Children() As MetisNamespaceList
            Get
                Return _Children
            End Get
        End Property

        ''' <summary>Indicates if the Namespace was changed since last save.</summary>
        ''' <remarks>Will also return true for new unsaved objects.</remarks>
        Public ReadOnly Property IsModified() As Boolean
            Get
                Return (_Annotation <> _BackupAnnotation OrElse _Name <> _BackupName OrElse _ID < 0)
            End Get
        End Property

        ''' <summary>Save the Namespace to the DB</summary>
        ''' <remarks>The Namesapce will be reloaded from the DB after save.</remarks>
        Public Sub Save()
            Dim tmpParentID As Integer = -1
            If _Parent IsNot Nothing Then tmpParentID = _Parent.ID
            If _ID < 0 Then _ID = _MetisCore.GetNextID

            Dim tmpStr As String = String.Format("MERGE INTO dam_namespaces trg " & _
                                                 " USING (SELECT {0} id, '{1}' descr, '{2}' annot, DECODE({3},-1,NULL,{3}) parent FROM dual) src " & _
                                                 " ON (trg.namespace_id=src.id) " & _
                                                 "WHEN MATCHED THEN " & _
                                                 " UPDATE SET trg.description = src.descr, trg.annotation = src.annot, trg.parent_namespace_fk=src.parent " & _
                                                 "WHEN NOT MATCHED THEN " & _
                                                 " INSERT (namespace_id, description, annotation, parent_namespace_fk) " & _
                                                 " VALUES (src.id, src.descr, src.annot, src.parent)", _ID, _Name, _Annotation.Replace("'", "''"), tmpParentID)

            Dim OraCmd As New OracleCommand(tmpStr, _MetisCore.Connection)
            OraCmd.ExecuteNonQuery()
            Reload()
        End Sub

        ''' <summary>Delete the Namespace from DB.</summary>
        ''' <remarks>The Namespace will also be removed from its parent child-list.</remarks>
        Public Sub Drop()
            Dim tmpStr As String = String.Format("DELETE FROM dam_namespaces WHERE namespace_id={0}", _ID)
            Dim OraCmd As New OracleCommand(tmpStr, _MetisCore.Connection)
            OraCmd.ExecuteNonQuery()
            If _Parent IsNot Nothing Then
                _Parent.Children.Remove(Me)
            Else
                _MetisCore.Namespaces.Remove(Me)
            End If

        End Sub

        ''' <summary>Reloads the Namespace from DB.</summary>
        ''' <remarks>The Namespace will also  update its parent child-list.</remarks>
        Public Sub Reload()
            Dim OraCmd As New OracleCommand(String.Format("SELECT namespace_id, description, annotation, NVL(parent_namespace_fk,-1) " & _
                                                          "FROM dam_namespaces WHERE namespace_id={0}", _ID), _MetisCore.Connection)

            Dim OraReader As OracleDataReader = OraCmd.ExecuteReader
            If OraReader.Read Then
                _ID = OraReader.GetInt32(0)
                _Name = OraReader.GetString(1)

                Try
                    _Annotation = OraReader.GetString(2)
                Catch ex As Exception
                    _Annotation = ""
                End Try


                Dim tmpParenNamespace As MetisNamespace = _MetisCore.Namespaces.Find(OraReader.GetInt32(3))
                If (_Parent IsNot Nothing AndAlso Not _Parent.Equals(tmpParenNamespace)) OrElse _
                   (tmpParenNamespace IsNot Nothing AndAlso Not tmpParenNamespace.Equals(_Parent)) Then

                    If _Parent IsNot Nothing Then
                        _Parent.Children.Remove(Me)
                    Else
                        _MetisCore.Namespaces.Remove(Me)
                    End If

                    If tmpParenNamespace IsNot Nothing Then
                        tmpParenNamespace.Children.Add(Me)
                    Else
                        _MetisCore.Namespaces.Add(Me)
                    End If

                    RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Parent"))
                End If


                If _Annotation <> _BackupAnnotation Then RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Annotation"))
                If _Name <> _BackupName Then RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("Name"))

                _BackupAnnotation = _Annotation
                _BackupName = _Name
                _BackupParent = _Parent

            End If

            OraReader.Close()
        End Sub

        ''' <summary>The complete path of that Namespace</summary>
        ''' <returns>A string with all parent Namespaces separated by '.'</returns>
        Public Overrides Function ToString() As String
            Dim ParentStr As String = ""
            If _Parent IsNot Nothing Then ParentStr = _Parent.ToString & "."
            Return ParentStr & _Name
        End Function

        ''' <summary>Compares the Namespace to another Namespace by its name.</summary>
        ''' <param name="other">The Namespace to compare to.</param>
        ''' <returns>
        ''' <list type="table">
        ''' <listheader><term>Return value</term><description>Description</description></listheader>  
        ''' <item><term>-1</term><description>The other Namespace name is "larger"</description></item>
        ''' <item><term>0</term><description>The names are equal</description></item>
        ''' <item><term>1</term><description>The this Namespace name is "larger"</description></item>
        ''' </list>
        ''' The comparison is case-insensitive.
        ''' </returns>
        Public Function CompareTo(ByVal other As MetisNamespace) As Integer Implements System.IComparable(Of MetisNamespace).CompareTo
            If Me._Name > other._Name Then
                Return 1
            ElseIf Me._Name < other._Name Then
                Return -1
            Else
                Return 0
            End If
        End Function

        Public Sub Move(ByVal TargetParentNamespace As MetisNamespace)
            Try
                Me.BeginEdit()

                Dim SrcParentChilds As Core.Functions.MetisNamespaceList
                If Me.Parent IsNot Nothing Then
                    SrcParentChilds = Me.Parent.Children
                Else
                    SrcParentChilds = _MetisCore.Namespaces
                End If

                SrcParentChilds.Remove(Me)
                If TargetParentNamespace IsNot Nothing Then
                    TargetParentNamespace.Children.Add(Me)
                Else
                    _MetisCore.Namespaces.Add(Me)
                End If

            Catch ex As Core.Exceptions.MetisNameException
                Me.CancelEdit()
                Throw ex
            Finally
                Me.EndEdit()
            End Try
        End Sub

        ''' <summary>Indicates if the Namespace is in Edit-Mode (<see cref="BeginEdit"/> was called)</summary>
        Public ReadOnly Property IsEditMode() As Boolean
            Get
                Return _IsEditMode
            End Get
        End Property

        Public Overrides Function Equals(ByVal obj As Object) As Boolean
            If TypeOf obj Is MetisNamespace AndAlso DirectCast(obj, MetisNamespace).ID = _ID Then
                Return True
            Else
                Return MyBase.Equals(obj)
            End If
        End Function

        Public Function CompareTo(ByVal other As IMetisCommonEntity) As Integer Implements System.IComparable(Of IMetisCommonEntity).CompareTo
            Return String.Compare(_Name, other.Name, True)
        End Function

        ''' <summary>Start Edit-Mode</summary>
        ''' <remarks>No <see cref="PropertyChanged"/>-Events will be raised while in edit mode.</remarks>
        Public Sub BeginEdit() Implements System.ComponentModel.IEditableObject.BeginEdit
            _IsEditMode = True
        End Sub

        ''' <summary>End Edit-Mode and reset properties to their former values.</summary>
        Public Sub CancelEdit() Implements System.ComponentModel.IEditableObject.CancelEdit
            If _IsEditMode Then
                _IsEditMode = False
                _Annotation = _BackupAnnotation
                _Name = _BackupName
                _Parent = _BackupParent
            End If
        End Sub

        ''' <summary>End Edit-Mode</summary>
        ''' <remarks>The Namespace will be saved to the DB afterwards.</remarks>
        Public Sub EndEdit() Implements System.ComponentModel.IEditableObject.EndEdit
            If _IsEditMode Then
                _IsEditMode = False
                Save()
            End If
        End Sub
    End Class


End Namespace

